package com.zusmart.base.network.message.fixedlength;

import java.util.ArrayList;
import java.util.List;

import com.zusmart.base.buffer.Buffer;
import com.zusmart.base.buffer.support.ByteArrayBuffer;
import com.zusmart.base.buffer.support.LinkedBuffer;
import com.zusmart.base.network.message.Message;
import com.zusmart.base.network.message.MessageEncodeException;
import com.zusmart.base.network.message.MessageProtocol;
import com.zusmart.base.network.message.support.SkipMessage;

/**
 * 支持大文件分包,但不建议传输大文件
 * 
 * @author Administrator
 *
 */
public class FixedLengthMessageProtocol implements MessageProtocol {

	private static final int HEAD_LENGTH = 4 + 1 + 4 + 4;

	private static final byte MSG_TYPE_HEAD = 1;
	private static final byte MSG_TYPE_BODY = 2;
	private static final byte MSG_TYPE_FOOT = 3;
	private static final byte MSG_TYPE_FULL = 4;

	private final int maxLength;

	private byte[] head;
	private byte[] foot;
	private List<byte[]> body;

	public FixedLengthMessageProtocol(int maxLength) {
		this.maxLength = maxLength;
	}

	public FixedLengthMessageProtocol() {
		this.maxLength = 1024 * 8;
	}

	@Override
	public Buffer encode(Message message) throws Exception {
		if (null == message) {
			return null;
		}
		if (message instanceof FixedLengthMessage) {
			FixedLengthMessage msg = (FixedLengthMessage) message;
			if (null == msg.getContent() || msg.getContent().length == 0) {
				throw new MessageEncodeException("message content is blank");
			}
			return split(this.maxLength, msg.getContent());
		}
		return null;
	}

	@Override
	public Message decode(Buffer buffer) throws Exception {
		if (null == buffer) {
			return null;
		}
		if (buffer.remaining() < HEAD_LENGTH) {
			return null;
		}
		int length = buffer.getInt();
		if (length < 0) {
			return null;
		}
		byte type = buffer.get();
		int start = buffer.getInt();
		int end = buffer.getInt();
		byte[] content = new byte[end - start];
		if (type == MSG_TYPE_FULL) {
			buffer.get(content);
			return new FixedLengthMessage(content);
		} else if (type == MSG_TYPE_HEAD) {
			buffer.get(content);
			this.head = content;
			return SkipMessage.INSTANCE;
		} else if (type == MSG_TYPE_FOOT) {
			buffer.get(content);
			this.foot = content;
			byte[] value = merge(this.head, this.foot, this.body);
			this.head = null;
			this.foot = null;
			this.body = null;
			return new FixedLengthMessage(value);
		} else {
			buffer.get(content);
			if (null == this.body) {
				this.body = new ArrayList<byte[]>();
			}
			this.body.add(content);
			return SkipMessage.INSTANCE;
		}
	}

	private static final byte[] merge(byte[] head, byte[] foot, List<byte[]> body) {
		int total = 0;
		if (null != head) {
			total += head.length;
		}
		if (null != foot) {
			total += foot.length;
		}
		if (null != body && body.size() > 0) {
			for (byte[] data : body) {
				total += data.length;
			}
		}
		byte[] content = new byte[total];
		int start = 0;
		if (null != head) {
			System.arraycopy(head, 0, content, start, head.length);
			start += head.length;
		}
		if (null != body && body.size() > 0) {
			for (byte[] data : body) {
				System.arraycopy(data, 0, content, start, data.length);
				start += data.length;
			}
		}
		if (null != foot) {
			System.arraycopy(foot, 0, content, start, foot.length);
			start += foot.length;
		}
		return content;
	}

	private static final Buffer split(int maxLength, byte[] content) {
		int length = content.length;
		int packet = maxLength - HEAD_LENGTH;// 单包总包长度 - 头信息长度
		if (length < maxLength) {
			Buffer buffer = ByteArrayBuffer.allocate(length + HEAD_LENGTH);
			buffer.putInt(length);
			buffer.put(MSG_TYPE_FULL);
			buffer.putInt(0);
			buffer.putInt(length);
			buffer.put(content);
			return buffer;
		}
		int page = (length + packet - 1) / packet;
		Buffer[] buffers = new Buffer[page];
		for (int i = 0; i < buffers.length; i++) {
			int start = i * packet;
			int end = i + 1 == page ? length : (i + 1) * packet;
			int len = end - start;
			Buffer buffer = ByteArrayBuffer.allocate(len + HEAD_LENGTH);
			buffer.putInt(length);
			if (i == 0) {
				buffer.put(MSG_TYPE_HEAD);
			} else if (i == page - 1) {
				buffer.put(MSG_TYPE_FOOT);
			} else {
				buffer.put(MSG_TYPE_BODY);
			}
			byte[] temp = new byte[len];
			System.arraycopy(content, start, temp, 0, temp.length);
			buffer.putInt(start);
			buffer.putInt(end);
			buffer.put(temp);
			buffers[i] = buffer.flip();
		}
		Buffer buffer = new LinkedBuffer(buffers);
		buffer.position(buffer.limit());
		return buffer;
	}

}